| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 1 | <!doctype html> | 
 | 2 | <meta charset=utf-8> | 
 | 3 | <title>RTCPeerConnection Set Session Description - Transceiver Tests</title> | 
 | 4 | <script src="/resources/testharness.js"></script> | 
 | 5 | <script src="/resources/testharnessreport.js"></script> | 
 | 6 | <script src="RTCPeerConnection-helper.js"></script> | 
 | 7 | <script> | 
 | 8 |  'use strict'; | 
 | 9 |  | 
 | 10 |  // Test is based on the following editor draft: | 
 | 11 |  // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html | 
 | 12 |  | 
 | 13 |  // The following helper functions are called from RTCPeerConnection-helper.js: | 
 | 14 |  // generateAnswer | 
 | 15 |  | 
 | 16 |  /* | 
 | 17 |  4.3.2. Interface Definition | 
 | 18 |  | 
 | 19 |  [Constructor(optional RTCConfiguration configuration)] | 
 | 20 |  interface RTCPeerConnection : EventTarget { | 
 | 21 |  Promise<void> setLocalDescription( | 
 | 22 |  RTCSessionDescriptionInit description); | 
 | 23 |  | 
 | 24 |  Promise<void> setRemoteDescription( | 
 | 25 |  RTCSessionDescriptionInit description); | 
 | 26 |  ... | 
 | 27 |  }; | 
 | 28 |  | 
 | 29 |  4.6.2. RTCSessionDescription Class | 
 | 30 |  dictionary RTCSessionDescriptionInit { | 
 | 31 |  required RTCSdpType type; | 
 | 32 |  DOMString sdp = ""; | 
 | 33 |  }; | 
 | 34 |  | 
 | 35 |  4.6.1. RTCSdpType | 
 | 36 |  enum RTCSdpType { | 
 | 37 |  "offer", | 
 | 38 |  "pranswer", | 
 | 39 |  "answer", | 
 | 40 |  "rollback" | 
 | 41 |  }; | 
 | 42 |  | 
 | 43 |  5.4. RTCRtpTransceiver Interface | 
 | 44 |  | 
 | 45 |  interface RTCRtpTransceiver { | 
 | 46 |  readonly attribute DOMString? mid; | 
 | 47 |  [SameObject] | 
 | 48 |  readonly attribute RTCRtpSender sender; | 
 | 49 |  [SameObject] | 
 | 50 |  readonly attribute RTCRtpReceiver receiver; | 
 | 51 |  readonly attribute RTCRtpTransceiverDirection direction; | 
 | 52 |  readonly attribute RTCRtpTransceiverDirection? currentDirection; | 
 | 53 |  ... | 
 | 54 |  }; | 
 | 55 |  */ | 
 | 56 |  | 
 | 57 |  /* | 
 | 58 |  4.3.1.6. Set the RTCSessionSessionDescription | 
 | 59 |  7. If description is set as a local description, then run the following steps for | 
 | 60 |  each media description in description that is not yet associated with an | 
 | 61 |  RTCRtpTransceiver object: | 
 | 62 |  1. Let transceiver be the RTCRtpTransceiver used to create the media | 
 | 63 |  description. | 
 | 64 |  2. Set transceiver's mid value to the mid of the corresponding media | 
 | 65 |  description. | 
 | 66 |  */ | 
| Philipp Hancke | 45f9422 | 2018-05-08 14:58:33 | [diff] [blame] | 67 |  promise_test(t => { | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 68 |  const pc = new RTCPeerConnection(); | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame] | 69 |  t.add_cleanup(() => pc.close()); | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 70 |  const transceiver = pc.addTransceiver('audio'); | 
 | 71 |  assert_equals(transceiver.mid, null); | 
 | 72 |  | 
 | 73 |  return pc.createOffer() | 
| Soares Chen | f6457c8 | 2017-07-13 04:27:42 | [diff] [blame] | 74 |  .then(offer => { | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 75 |  assert_equals(transceiver.mid, null, | 
 | 76 |  'Expect transceiver.mid to still be null after createOffer'); | 
 | 77 |  | 
| Soares Chen | f6457c8 | 2017-07-13 04:27:42 | [diff] [blame] | 78 |  return pc.setLocalDescription(offer) | 
 | 79 |  .then(() => { | 
 | 80 |  assert_equals(typeof transceiver.mid, 'string', | 
 | 81 |  'Expect transceiver.mid to set to valid string value'); | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 82 |  | 
| Soares Chen | f6457c8 | 2017-07-13 04:27:42 | [diff] [blame] | 83 |  assert_equals(offer.sdp.includes(`\r\na=mid:${transceiver.mid}`), true, | 
 | 84 |  'Expect transceiver mid to be found in offer SDP'); | 
 | 85 |  }); | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 86 |  }); | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 87 |  }, 'setLocalDescription(offer) with m= section should assign mid to corresponding transceiver'); | 
 | 88 |  | 
 | 89 |  /* | 
 | 90 |  4.3.1.6. Set the RTCSessionSessionDescription | 
 | 91 |  8. If description is set as a remote description, then run the following steps | 
 | 92 |  for each media description in description: | 
 | 93 |  2. If no suitable transceiver is found (transceiver is unset), run the following | 
 | 94 |  steps: | 
 | 95 |  1. Create an RTCRtpSender, sender, from the media description. | 
 | 96 |  2. Create an RTCRtpReceiver, receiver, from the media description. | 
 | 97 |  3. Create an RTCRtpTransceiver with sender, receiver and direction, and let | 
 | 98 |  transceiver be the result. | 
 | 99 |  3. Set transceiver's mid value to the mid of the corresponding media description. | 
 | 100 |  */ | 
| Philipp Hancke | 45f9422 | 2018-05-08 14:58:33 | [diff] [blame] | 101 |  promise_test(t => { | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 102 |  const pc1 = new RTCPeerConnection(); | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame] | 103 |  t.add_cleanup(() => pc1.close()); | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 104 |  const pc2 = new RTCPeerConnection(); | 
 | 105 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame] | 106 |  t.add_cleanup(() => pc2.close()); | 
 | 107 |  | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 108 |  const transceiver1 = pc1.addTransceiver('audio'); | 
 | 109 |  assert_array_equals(pc1.getTransceivers(), [transceiver1]); | 
 | 110 |  assert_array_equals(pc2.getTransceivers(), []); | 
 | 111 |  | 
 | 112 |  return pc1.createOffer() | 
 | 113 |  .then(offer => { | 
 | 114 |  return Promise.all([ | 
 | 115 |  pc1.setLocalDescription(offer), | 
| Jan-Ivar Bruaroey | d6b819d | 2018-05-14 21:24:57 | [diff] [blame] | 116 |  pc2.setRemoteDescription(offer) | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 117 |  ]) | 
 | 118 |  .then(() => { | 
 | 119 |  const transceivers = pc2.getTransceivers(); | 
 | 120 |  assert_equals(transceivers.length, 1, | 
 | 121 |  'Expect new transceiver added to pc2 after setRemoteDescription'); | 
 | 122 |  | 
 | 123 |  const [ transceiver2 ] = transceivers; | 
 | 124 |  | 
 | 125 |  assert_equals(typeof transceiver2.mid, 'string', | 
 | 126 |  'Expect transceiver2.mid to be set'); | 
 | 127 |  | 
 | 128 |  assert_equals(transceiver1.mid, transceiver2.mid, | 
 | 129 |  'Expect transceivers of both side to have the same mid'); | 
 | 130 |  | 
 | 131 |  assert_equals(offer.sdp.includes(`\r\na=mid:${transceiver2.mid}`), true, | 
 | 132 |  'Expect transceiver mid to be found in offer SDP'); | 
 | 133 |  }); | 
 | 134 |  }); | 
 | 135 |  }, 'setRemoteDescription(offer) with m= section and no existing transceiver should create corresponding transceiver'); | 
 | 136 |  | 
 | 137 |  /* | 
 | 138 |  4.3.1.6. Set the RTCSessionSessionDescription | 
 | 139 |  9. If description is of type "rollback", then run the following steps: | 
 | 140 |  1. If the mid value of an RTCRtpTransceiver was set to a non-null value by | 
 | 141 |  the RTCSessionDescription that is being rolled back, set the mid value | 
 | 142 |  of that transceiver to null, as described by [JSEP] (section 4.1.8.2.). | 
 | 143 |  */ | 
| Philipp Hancke | 45f9422 | 2018-05-08 14:58:33 | [diff] [blame] | 144 |  promise_test(t => { | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 145 |  const pc = new RTCPeerConnection(); | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame] | 146 |  t.add_cleanup(() => pc.close()); | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 147 |  const transceiver = pc.addTransceiver('audio'); | 
 | 148 |  assert_equals(transceiver.mid, null); | 
 | 149 |  | 
 | 150 |  return pc.createOffer() | 
 | 151 |  .then(offer => { | 
 | 152 |  assert_equals(transceiver.mid, null); | 
 | 153 |  return pc.setLocalDescription(offer); | 
 | 154 |  }) | 
 | 155 |  .then(() => { | 
 | 156 |  assert_not_equals(transceiver.mid, null); | 
 | 157 |  return pc.setLocalDescription({ type: 'rollback' }); | 
 | 158 |  }) | 
 | 159 |  .then(() => { | 
 | 160 |  assert_equals(transceiver.mid, null, | 
 | 161 |  'Expect transceiver.mid to become null again after rollback'); | 
 | 162 |  }); | 
 | 163 |  }, 'setLocalDescription(rollback) should unset transceiver.mid'); | 
 | 164 |  | 
| Philipp Hancke | 45f9422 | 2018-05-08 14:58:33 | [diff] [blame] | 165 |  promise_test(t => { | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 166 |  const pc = new RTCPeerConnection(); | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame] | 167 |  t.add_cleanup(() => pc.close()); | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 168 |  const transceiver1 = pc.addTransceiver('audio'); | 
 | 169 |  assert_equals(transceiver1.mid, null); | 
 | 170 |  | 
 | 171 |  return pc.createOffer() | 
| Soares Chen | f6457c8 | 2017-07-13 04:27:42 | [diff] [blame] | 172 |  .then(offer => | 
 | 173 |  pc.setLocalDescription(offer) | 
 | 174 |  .then(() => generateAnswer(offer))) | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 175 |  .then(answer => pc.setRemoteDescription(answer)) | 
 | 176 |  .then(() => { | 
 | 177 |  // pc is back to stable state | 
 | 178 |  // create another transceiver | 
 | 179 |  const transceiver2 = pc.addTransceiver('video'); | 
 | 180 |  | 
 | 181 |  assert_not_equals(transceiver1.mid, null); | 
 | 182 |  assert_equals(transceiver2.mid, null); | 
 | 183 |  | 
 | 184 |  return pc.createOffer() | 
 | 185 |  .then(offer => pc.setLocalDescription(offer)) | 
 | 186 |  .then(() => { | 
 | 187 |  assert_not_equals(transceiver1.mid, null); | 
 | 188 |  assert_not_equals(transceiver2.mid, null, | 
 | 189 |  'Expect transceiver2.mid to become set'); | 
 | 190 |  | 
 | 191 |  return pc.setLocalDescription({ type: 'rollback' }); | 
 | 192 |  }) | 
 | 193 |  .then(() => { | 
 | 194 |  assert_not_equals(transceiver1.mid, null, | 
 | 195 |  'Expect transceiver1.mid to stay set'); | 
 | 196 |  | 
 | 197 |  assert_equals(transceiver2.mid, null, | 
 | 198 |  'Expect transceiver2.mid to be rolled back to null'); | 
 | 199 |  }); | 
 | 200 |  }) | 
 | 201 |  }, 'setLocalDescription(rollback) should only unset transceiver mids associated with current round'); | 
 | 202 |  | 
 | 203 |  /* | 
 | 204 |  4.3.1.6. Set the RTCSessionSessionDescription | 
 | 205 |  9. If description is of type "rollback", then run the following steps: | 
 | 206 |  2. If an RTCRtpTransceiver was created by applying the RTCSessionDescription | 
 | 207 |  that is being rolled back, and a track has not been attached to it via | 
 | 208 |  addTrack, remove that transceiver from connection's set of transceivers, | 
 | 209 |  as described by [JSEP] (section 4.1.8.2.). | 
 | 210 |  */ | 
| Philipp Hancke | 45f9422 | 2018-05-08 14:58:33 | [diff] [blame] | 211 |  promise_test(t => { | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 212 |  const pc1 = new RTCPeerConnection(); | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame] | 213 |  t.add_cleanup(() => pc1.close()); | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 214 |  const pc2 = new RTCPeerConnection(); | 
 | 215 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame] | 216 |  t.add_cleanup(() => pc2.close()); | 
 | 217 |  | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 218 |  pc1.addTransceiver('audio'); | 
 | 219 |  | 
 | 220 |  return pc1.createOffer() | 
| Soares Chen | f6457c8 | 2017-07-13 04:27:42 | [diff] [blame] | 221 |  .then(offer => pc2.setRemoteDescription(offer)) | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 222 |  .then(() => { | 
 | 223 |  const transceivers = pc2.getTransceivers(); | 
 | 224 |  assert_equals(transceivers.length, 1); | 
 | 225 |  const [ transceiver ] = transceivers; | 
 | 226 |  | 
 | 227 |  assert_equals(typeof transceiver.mid, 'string', | 
 | 228 |  'Expect transceiver.mid to be set'); | 
 | 229 |  | 
 | 230 |  return pc2.setRemoteDescription({ type: 'rollback' }) | 
 | 231 |  .then(() => { | 
 | 232 |  assert_equals(transceiver.mid, null, | 
 | 233 |  'Expect transceiver.mid to be unset'); | 
 | 234 |  | 
 | 235 |  assert_array_equals(pc2.getTransceivers(), [], | 
 | 236 |  `Expect transceiver to be removed from pc2's transceiver list`); | 
 | 237 |  }); | 
 | 238 |  }); | 
 | 239 |  }, 'setRemoteDescription(rollback) should remove newly created transceiver from transceiver list'); | 
 | 240 |  | 
| youennf | 1db8f79 | 2018-12-14 03:21:45 | [diff] [blame] | 241 |  promise_test(async t => { | 
 | 242 |  const pc1 = new RTCPeerConnection(); | 
 | 243 |  t.add_cleanup(() => pc1.close()); | 
 | 244 |  const pc2 = new RTCPeerConnection(); | 
 | 245 |  t.add_cleanup(() => pc2.close()); | 
 | 246 |  | 
 | 247 |  pc1.addTransceiver('audio'); | 
 | 248 |  const offer = await pc1.createOffer(); | 
 | 249 |  await pc1.setLocalDescription(offer); | 
 | 250 |  | 
 | 251 |  assert_false(pc1.getTransceivers()[0].stopped, 'Transceiver is not stopped'); | 
 | 252 |  | 
 | 253 |  await pc2.setRemoteDescription(offer); | 
 | 254 |  pc2.getTransceivers()[0].stop(); | 
 | 255 |  const answer = await pc2.createAnswer(); | 
 | 256 |  | 
 | 257 |  await pc1.setRemoteDescription(answer); | 
 | 258 |  | 
 | 259 |  assert_true(pc1.getTransceivers()[0].stopped, 'Transceiver is stopped'); | 
| youennf | e183d86 | 2018-12-14 23:25:41 | [diff] [blame] | 260 |  assert_equals(pc1.getReceivers().length, 0, 'getReceivers does not expose a receiver of a stopped transceiver'); | 
 | 261 |  assert_equals(pc1.getSenders().length, 0, 'getSenders does not expose a sender of a stopped transceiver'); | 
| youennf | 1db8f79 | 2018-12-14 03:21:45 | [diff] [blame] | 262 |  }, 'setRemoteDescription should stop the transceiver if its corresponding m section is rejected'); | 
 | 263 |  | 
| Soares Chen | 55f80ea | 2017-07-11 06:56:58 | [diff] [blame] | 264 |  /* | 
 | 265 |  TODO | 
 | 266 |  - Steps for transceiver direction is added to tip of tree draft, but not yet | 
 | 267 |  published as editor's draft | 
 | 268 |  | 
 | 269 |  4.3.1.6. Set the RTCSessionSessionDescription | 
 | 270 |  8. If description is set as a remote description, then run the following steps | 
 | 271 |  for each media description in description: | 
 | 272 |  1. As described by [JSEP] (section 5.9.), attempt to find an existing | 
 | 273 |  RTCRtpTransceiver object, transceiver, to represent the media description. | 
 | 274 |  3. If the media description has no MID, and transceiver's mid is unset, generate | 
 | 275 |  a random value as described in [JSEP] (section 5.9.). | 
 | 276 |  4. If the direction of the media description is sendrecv or sendonly, and | 
 | 277 |  transceiver.receiver.track has not yet been fired in a track event, process | 
 | 278 |  the remote track for the media description, given transceiver. | 
 | 279 |  5. If the media description is rejected, and transceiver is not already stopped, | 
 | 280 |  stop the RTCRtpTransceiver transceiver. | 
 | 281 |  | 
 | 282 |  [JSEP] | 
 | 283 |  5.9. Applying a Remote Description | 
 | 284 |  - If the m= section is not associated with any RtpTransceiver | 
 | 285 |  (possibly because it was dissociated in the previous step), | 
 | 286 |  either find an RtpTransceiver or create one according to the | 
 | 287 |  following steps: | 
 | 288 |  | 
 | 289 |  - If the m= section is sendrecv or recvonly, and there are | 
 | 290 |  RtpTransceivers of the same type that were added to the | 
 | 291 |  PeerConnection by addTrack and are not associated with any | 
 | 292 |  m= section and are not stopped, find the first (according to | 
 | 293 |  the canonical order described in Section 5.2.1) such | 
 | 294 |  RtpTransceiver. | 
 | 295 |  | 
 | 296 |  - If no RtpTransceiver was found in the previous step, create | 
 | 297 |  one with a recvonly direction. | 
 | 298 |  */ | 
 | 299 | </script> |